<template>
  <div ref="container" class="house-model-container"></div>
</template>

<script setup lang="ts">
// Vue 组合式 API 导入
import { ref, onMounted, onUnmounted } from 'vue';
// Three.js 3D 图形库导入
import * as THREE from 'three';
// 轨道控制器导入，用于鼠标控制3D场景的旋转、平移和缩放
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

// 容器引用，用于挂载 Three.js 渲染器的 DOM 元素
const container = ref<HTMLDivElement | null>(null);

// Three.js 核心对象声明
let scene: THREE.Scene | null = null;          // 3D 场景，包含所有要渲染的对象
let camera: THREE.PerspectiveCamera | null = null; // 透视相机，模拟人眼视角
let renderer: THREE.WebGLRenderer | null = null;   // WebGL 渲染器，负责将3D场景渲染到2D画布
let controls: OrbitControls | null = null;     // 轨道控制器，允许用户用鼠标交互
let animationId: number | null = null;         // 动画帧ID，用于控制动画循环

// 点坐标接口，用于定义墙段的起点和终点
interface Point {
  x: number;  // x 坐标（水平方向）
  z: number;  // z 坐标（深度方向）
}

/**
 * 根据两点创建墙体的函数
 * @param start - 起点坐标 {x, z}
 * @param end - 终点坐标 {x, z}
 * @param height - 墙高，默认3米
 * @param thickness - 墙厚，默认0.2米
 */
function createWallFromPoints(start: Point, end: Point, height: number = 3, thickness: number = 0.2): void {
  // 安全检查：确保场景已初始化
  if (!scene) {return;}

  // 计算两点之间的差值
  const dx = end.x - start.x;  // x方向差值
  const dz = end.z - start.z;  // z方向差值
  
  // 计算墙体的实际长度（使用勾股定理）
  const length = Math.hypot(dx, dz);
  
  // 创建立方体几何体（长方体形状的墙）
  // BoxGeometry(长度, 高度, 厚度)
  const geometry = new THREE.BoxGeometry(length, height, thickness);
  
  // 创建材质（使用Lambert材质，支持光照效果）
  // MeshLambertMaterial 是一种会对光线产生反应的材质
  const material = new THREE.MeshLambertMaterial({ color: 0xffffff });
  
  // 创建网格对象（几何体 + 材质）
  const wall = new THREE.Mesh(geometry, material);
  
  // 设置墙体的位置（位于两点中间，高度的一半处）
  wall.position.set((start.x + end.x) / 2, height / 2, (start.z + end.z) / 2);
  
  // 设置墙体的旋转角度（使其朝向正确的方向）
  // Math.atan2(dz, dx) 计算两点连线与x轴的夹角
  wall.rotation.y = Math.atan2(dz, dx);
  
  // 将墙体添加到场景中
  scene.add(wall);
}

/**
 * 初始化 Three.js 场景
 * 这个函数负责创建场景、相机、渲染器、灯光和3D对象
 */
async function init(): Promise<void> {
  // 安全检查：确保容器DOM元素存在
  if (!container.value) {return;}

  // 获取容器的宽度和高度
  const width = container.value.clientWidth;
  const height = container.value.clientHeight;

  // 创建3D场景（就像创建一个空的3D世界）
  scene = new THREE.Scene();
  // 设置场景背景色为温馨的米色，营造家庭氛围
  scene.background = new THREE.Color(0xf5f5dc);
  
  // 创建透视相机（模拟人眼视角）
  // PerspectiveCamera(视野角度, 宽高比, 近裁剪面, 远裁剪面)
  camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
  
  // 创建WebGL渲染器（负责将3D场景绘制到2D画布上）
  // antialias: true 开启抗锯齿，让边缘更平滑
  renderer = new THREE.WebGLRenderer({ antialias: true });
  
  // 设置渲染器大小（匹配容器尺寸）
  renderer.setSize(width, height);
  
  // 将渲染器的canvas元素添加到DOM容器中
  container.value.appendChild(renderer.domElement);

  // 创建轨道控制器（允许用户用鼠标旋转、平移和缩放场景）
  controls = new OrbitControls(camera, renderer.domElement);

  // 添加环境光（均匀照亮整个场景，没有方向性）
  // AmbientLight(颜色, 强度)
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
  scene.add(ambientLight);
  
  // 添加定向光（模拟太阳光，有方向和阴影效果）
  // DirectionalLight(颜色, 强度)
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
  // 设置光源位置 (x, y, z)
  directionalLight.position.set(10, 20, 10);
  scene.add(directionalLight);

  // 创建地板几何体（一个平面）
  // PlaneGeometry(宽度, 高度)
  const floorGeometry = new THREE.PlaneGeometry(15, 15);
  
  // 创建地板材质（瓷砖效果）
  // 使用 MeshStandardMaterial 以获得更好的光照效果和材质属性
  // color: 浅米色瓷砖颜色, roughness: 粗糙度(0.3表示略有光泽), metalness: 金属度(0.1表示轻微金属感)
  const floorMaterial = new THREE.MeshStandardMaterial({
    color: 0xd8caaf,
    roughness: 0.3,
    metalness: 0.1,
  });
  
  // 创建地板网格对象
  const floor = new THREE.Mesh(floorGeometry, floorMaterial);
  
  // 旋转地板使其水平（默认平面是垂直的）
  // -Math.PI / 2 表示绕x轴旋转-90度
  floor.rotation.x = -Math.PI / 2;
  
  // 设置地板位置（将地板移动到场景中心偏下的位置）
  floor.position.set(6, 0, 5);
  
  // 将地板添加到场景中
  scene.add(floor);

  // 添加方向指示标签（东南西北）
  createDirectionLabels();

  // 基于近似平面图布局定义墙段
  const wallSegments: [Point, Point][] = [
    // 外墙（近似匹配不规则形状）
    [{ x: 0, z: 0 }, { x: 0, z: 10 }], // 左侧外墙
    [{ x: 0, z: 10 }, { x: 10, z: 10 }], // 顶部外墙
    [{ x: 10, z: 10 }, { x: 10, z: 4 }], // 右侧外墙上部
    [{ x: 10, z: 4 }, { x: 12, z: 4 }], // 右下阳台扩展
    [{ x: 12, z: 4 }, { x: 12, z: 0 }], // 右侧下部
    [{ x: 12, z: 0 }, { x: 5, z: 0 }], // 右下到入口
    [{ x: 5, z: 0 }, { x: 5, z: 1 }], // 入口右侧向上
    [{ x: 3, z: 0 }, { x: 3, z: 1 }], // 入口左侧向上
    [{ x: 3, z: 0 }, { x: 5, z: 0 }], // 入口底部
    [{ x: 0, z: 1 }, { x: 3, z: 1 }], // 左下到入口
    [{ x: 5, z: 1 }, { x: 12, z: 1 }], // 从入口的右下（调整后）

    // 内墙
    [{ x: 4, z: 8 }, { x: 4, z: 10 }], // 厨房和餐厅之间
    [{ x: 6, z: 8 }, { x: 6, z: 10 }], // 餐厅和顶部卧室之间
    [{ x: 0, z: 8 }, { x: 10, z: 8 }], // 顶部房间底部（与客厅分隔）
    [{ x: 2, z: 5 }, { x: 2, z: 8 }], // 左侧浴室右侧
    [{ x: 0, z: 5 }, { x: 2, z: 5 }], // 左侧浴室底部
    [{ x: 8, z: 5 }, { x: 8, z: 8 }], // 中间阳台左侧
    [{ x: 8, z: 5 }, { x: 10, z: 5 }], // 中间阳台底部
    [{ x: 0, z: 4 }, { x: 4, z: 4 }], // 左下卧室顶部（客厅底部）
    [{ x: 4, z: 1 }, { x: 4, z: 4 }], // 左下卧室右侧
    [{ x: 4, z: 1 }, { x: 6, z: 1 }], // 底部浴室底部
    [{ x: 6, z: 1 }, { x: 6, z: 4 }], // 底部浴室右侧 / 底部阳台左侧
    [{ x: 6, z: 1 }, { x: 12, z: 1 }], // 底部扩展
    [{ x: 6, z: 4 }, { x: 12, z: 4 }], // 底部阳台顶部
  ];

  // 遍历所有墙段定义，创建每一面墙
  wallSegments.forEach(([start, end]) => {
    createWallFromPoints(start, end);
  });

  // 设置相机初始位置（从斜上方观察房屋）
  if (camera) {
    // camera.position.set(x, y, z)
    camera.position.set(6, 5, 15);
  }
  
  /**
   * 创建方向指示标签函数
   * 使用CanvasTexture创建中文方向标签，避免字体不支持中文的问题
   */
  function createDirectionLabels(): void {
    if (!scene) { return; }

    // 定义方向标签的文本和位置（使用中文，符合地图上北下南左西右东标准）
    const directions = [
      { text: '南', position: new THREE.Vector3(6, 1, 12.5) }, // 北 - 远离相机的一侧（z轴最大处）
      { text: '北', position: new THREE.Vector3(6, 1, -2.5) },  // 南 - 靠近相机的一侧（z轴最小处）
      { text: '东', position: new THREE.Vector3(13.5, 1, 5) },  // 东 - 相机视角的右侧（x轴最大处）
      { text: '西', position: new THREE.Vector3(-1.5, 1, 5) },   // 西 - 相机视角的左侧（x轴最小处）
    ];

    // 创建每个方向的文字标签
    directions.forEach(({ text, position }) => {
      // 创建canvas用于绘制文字
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      if (!context) {return;}

      // 设置canvas大小
      canvas.width = 256;
      canvas.height = 256;
      
      // 绘制文字（添加黑色边框增强对比度）
      context.font = 'bold 120px Arial, "Microsoft YaHei", sans-serif'; // 使用系统字体，支持中文
      context.textAlign = 'center';
      context.textBaseline = 'middle';
      
      // 先绘制黑色边框（描边）
      context.strokeStyle = '#000000'; // 黑色边框
      context.lineWidth = 8; // 边框宽度
      context.strokeText(text, canvas.width / 2, canvas.height / 2);
      
      // 再绘制白色填充文字
      context.fillStyle = '#ffffff'; // 白色文字
      context.fillText(text, canvas.width / 2, canvas.height / 2);
      
      // 创建纹理
      const texture = new THREE.CanvasTexture(canvas);
      
      // 创建平面几何体
      const geometry = new THREE.PlaneGeometry(1, 1);
      const material = new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true,
        side: THREE.DoubleSide,
      });
      
      // 创建网格对象
      const textPlane = new THREE.Mesh(geometry, material);
      
      // 设置位置
      textPlane.position.copy(position);
      
      // 让文字始终面向相机（在动画循环中更新）
      textPlane.userData.isDirectionLabel = true;
      
      // 将文字添加到场景中（确保scene不为null）
      if (scene) {
        scene.add(textPlane);
      }
    });
  }
  
  // 设置控制器目标点（相机看向的位置）
  if (controls) {
    // controls.target.set(x, y, z)
    controls.target.set(6, 0, 5);
  }
}

/**
 * 动画循环函数
 * 这个函数会不断被调用，实现场景的持续渲染
 */
function animate(): void {
  // 安全检查：确保场景、相机和渲染器都已初始化
  if (!scene || !camera || !renderer) {return;}

  // 请求下一帧动画（形成循环）
  // requestAnimationFrame 是浏览器提供的API，用于平滑动画
  animationId = requestAnimationFrame(animate);

  // 更新轨道控制器（处理用户输入）
  if (controls) {
    controls.update();
  }

  // 更新方向标签的朝向，使其始终面向相机（确保scene和camera不为null）
  if (scene && camera) {
    scene.traverse((child) => {
      if (child.userData.isDirectionLabel && child instanceof THREE.Mesh) {
        if (camera) {
          child.lookAt(camera.position);
        }
      }
    });
  }

  // 渲染场景（将3D场景绘制到2D画布上）
  renderer.render(scene, camera);
}

/**
 * 窗口调整大小处理函数
 * 当浏览器窗口大小改变时，调整相机和渲染器以适应新尺寸
 */
function onWindowResize(): void {
  // 安全检查：确保容器、相机和渲染器都已初始化
  if (!container.value || !camera || !renderer) {return;}

  // 获取新的容器尺寸
  const width = container.value.clientWidth;
  const height = container.value.clientHeight;

  // 更新相机的宽高比
  camera.aspect = width / height;
  
  // 更新相机的投影矩阵（必须调用此方法使更改生效）
  camera.updateProjectionMatrix();
  
  // 更新渲染器大小
  renderer.setSize(width, height);
}

/**
 * 清理资源函数
 * 在组件卸载时调用，释放 Three.js 占用的内存资源
 */
function cleanup(): void {
  // 停止动画循环
  if (animationId) {
    cancelAnimationFrame(animationId);
  }

  // 销毁轨道控制器
  if (controls) {
    controls.dispose();
  }

  // 销毁渲染器
  if (renderer) {
    renderer.dispose();
  }

  // 遍历场景中的所有对象，释放几何体和材质资源
  if (scene) {
    scene.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        // 释放几何体资源
        child.geometry.dispose();
        
        // 释放材质资源（处理单个材质和材质数组的情况）
        if (Array.isArray(child.material)) {
          child.material.forEach(mat => mat.dispose());
        } else {
          child.material.dispose();
        }
      }
    });
  }

  // 移除窗口调整大小的监听器
  window.removeEventListener('resize', onWindowResize);
}

// Vue 生命周期钩子
// 组件挂载时执行初始化
onMounted(async() => {
  // 初始化 Three.js 场景
  await init();
  
  // 启动动画循环
  animate();
  
  // 添加窗口调整大小的监听器
  window.addEventListener('resize', onWindowResize);
});

// 组件卸载时执行清理
onUnmounted(() => {
  // 释放 Three.js 资源，避免内存泄漏
  cleanup();
});
</script>

<style scoped>
.house-model-container {
  width: 100%;
  height: 100vh;
}
</style>